"""
考试管理模块 - 工具类
"""
import random
import math
from datetime import timedelta
from django.db.models import Q, Count, Avg, StdDev
from django.utils import timezone

from .models import Examination, ExaminationQuestion, ExaminationSession
from apps.question_bank.models import Question, QuestionBank, QuestionBankItem


class AutoPaperGenerator:
    """自动组卷生成器"""

    def __init__(self, question_bank, config):
        """
        初始化组卷生成器

        Args:
            question_bank: 题库对象
            config: 组卷配置 {
                'total_score': 总分,
                'question_count': 题目总数,
                'difficulty_distribution': 难度分布 {1: count, 2: count, ...},
                'type_distribution': 题型分布 {'choice': count, 'essay': count, ...},
                'score_distribution': 分值分布 {'choice': 2, 'essay': 10, ...},
                'avoid_same_type': bool,  # 避免同类型题目连续
                'random_seed': int,  # 随机种子
            }
        """
        self.question_bank = question_bank
        self.config = config

        # 设置随机种子
        if 'random_seed' in config:
            random.seed(config['random_seed'])

    def generate_paper(self):
        """生成试卷"""
        # 获取可用题目
        available_questions = self._get_available_questions()

        # 检查题目是否足够
        if not self._validate_questions(available_questions):
            raise ValueError("题库题目数量不足，无法生成试卷")

        # 根据模式组卷
        if self.config.get('smart_mode', False):
            questions = self._smart_paper_generation(available_questions)
        else:
            questions = self._random_paper_generation(available_questions)

        return questions

    def _get_available_questions(self):
        """获取可用题目"""
        items = QuestionBankItem.objects.filter(
            question_bank=self.question_bank,
            status='active'
        ).select_related('question').prefetch_related('question__tag_relations')

        questions = []
        for item in items:
            question = item.question

            # 只包含激活状态的题目
            if question.status != 'active':
                continue

            questions.append({
                'question': question,
                'effective_score': item.effective_score,
                'effective_difficulty': item.effective_difficulty,
                'question_item': item
            })

        return questions

    def _validate_questions(self, questions):
        """验证题目是否足够"""
        required_count = self.config.get('question_count', 0)
        if len(questions) < required_count:
            return False

        # 检查题型分布
        type_distribution = self.config.get('type_distribution', {})
        for q_type, count in type_distribution.items():
            available = len([q for q in questions if q['question'].type == q_type])
            if available < count:
                return False

        # 检查难度分布
        difficulty_distribution = self.config.get('difficulty_distribution', {})
        for difficulty, count in difficulty_distribution.items():
            available = len([q for q in questions if q['effective_difficulty'] == difficulty])
            if available < count:
                return False

        return True

    def _random_paper_generation(self, questions):
        """随机组卷"""
        selected_questions = []
        remaining_questions = questions.copy()

        # 按题型分布选择题目
        type_distribution = self.config.get('type_distribution', {})
        if type_distribution:
            for q_type, count in type_distribution.items():
                type_questions = [q for q in remaining_questions if q['question'].type == q_type]
                selected = random.sample(type_questions, min(count, len(type_questions)))
                selected_questions.extend(selected)

                # 从剩余题目中移除已选题目
                for q in selected:
                    remaining_questions.remove(q)

        # 按难度分布调整
        difficulty_distribution = self.config.get('difficulty_distribution', {})
        if difficulty_distribution:
            selected_questions = self._adjust_difficulty(selected_questions, difficulty_distribution)

        # 随机打乱顺序
        random.shuffle(selected_questions)

        return selected_questions

    def _smart_paper_generation(self, questions):
        """智能组卷"""
        # 计算题目难度系数
        for q in questions:
            q['difficulty_score'] = self._calculate_difficulty_score(q)

        # 按难度分组
        easy_questions = [q for q in questions if q['effective_difficulty'] <= 2]
        medium_questions = [q for q in questions if q['effective_difficulty'] == 3]
        hard_questions = [q for q in questions if q['effective_difficulty'] >= 4]

        # 按题型分组
        type_groups = {}
        for q in questions:
            q_type = q['question'].type
            if q_type not in type_groups:
                type_groups[q_type] = {'easy': [], 'medium': [], 'hard': []}

            if q['effective_difficulty'] <= 2:
                type_groups[q_type]['easy'].append(q)
            elif q['effective_difficulty'] == 3:
                type_groups[q_type]['medium'].append(q)
            else:
                type_groups[q_type]['hard'].append(q)

        selected_questions = []

        # 根据配置智能选择
        type_distribution = self.config.get('type_distribution', {})
        difficulty_distribution = self.config.get('difficulty_distribution', {})

        for q_type, count in type_distribution.items():
            if q_type not in type_groups:
                continue

            type_questions = type_groups[q_type]
            type_selected = []

            # 3:3:4 的易中难比例（可配置）
            easy_count = int(count * 0.3)
            medium_count = int(count * 0.3)
            hard_count = count - easy_count - medium_count

            # 根据难度分布调整
            if 'easy' in difficulty_distribution:
                easy_count = min(easy_count, difficulty_distribution['easy'])
            if 'medium' in difficulty_distribution:
                medium_count = min(medium_count, difficulty_distribution['medium'])
            if 'hard' in difficulty_distribution:
                hard_count = min(hard_count, difficulty_distribution['hard'])

            # 选择题目
            type_selected.extend(self._select_questions_by_group(
                type_questions['easy'], easy_count, 'easy'
            ))
            type_selected.extend(self._select_questions_by_group(
                type_questions['medium'], medium_count, 'medium'
            ))
            type_selected.extend(self._select_questions_by_group(
                type_questions['hard'], hard_count, 'hard'
            ))

            selected_questions.extend(type_selected)

        # 智能排序（避免同类型连续）
        selected_questions = self._smart_shuffle(selected_questions)

        return selected_questions

    def _calculate_difficulty_score(self, question):
        """计算题目难度系数"""
        base_score = question['effective_difficulty']

        # 考虑题目类型
        type_multipliers = {
            'choice': 0.8,
            'multiple_choice': 1.0,
            'true_false': 0.6,
            'fill_blank': 1.2,
            'short_answer': 1.3,
            'essay': 1.5,
            'calculation': 1.4
        }

        type_score = type_multipliers.get(question['question'].type, 1.0)

        # 考虑题目长度（主观题）
        if question['question'].type in ['essay', 'short_answer']:
            length_factor = min(len(question['question'].stem) / 200, 2.0)
            type_score *= length_factor

        return base_score * type_score

    def _select_questions_by_group(self, questions, count, group_name):
        """按组选择题目"""
        if not questions or count <= 0:
            return []

        # 按难度分数排序
        sorted_questions = sorted(questions, key=lambda x: x['difficulty_score'])

        # 使用分层抽样
        if len(sorted_questions) <= count:
            return sorted_questions

        # 将题目分成count个区间，每个区间选一个
        interval = len(sorted_questions) // count
        selected = []

        for i in range(count):
            index = i * interval + random.randint(0, min(interval, len(sorted_questions) - i * interval - 1))
            if index < len(sorted_questions):
                selected.append(sorted_questions.pop(index))

        return selected

    def _smart_shuffle(self, questions):
        """智能打乱题目顺序，避免同类型连续"""
        if not questions:
            return questions

        # 按题型分组
        type_groups = {}
        for i, q in enumerate(questions):
            q_type = q['question'].type
            if q_type not in type_groups:
                type_groups[q_type] = []
            type_groups[q_type].append((i, q))

        # 创建新顺序
        result = []
        type_queue = list(type_groups.keys())
        random.shuffle(type_queue)

        # 轮流从不同题型中选择题目
        type_indices = {q_type: 0 for q_type in type_queue}

        total_questions = sum(len(group) for group in type_groups.values())

        while len(result) < total_questions:
            # 找到还有未选题目的题型
            available_types = [
                q_type for q_type in type_queue
                if type_indices[q_type] < len(type_groups[q_type])
            ]

            if not available_types:
                break

            # 随机选择一个题型
            selected_type = random.choice(available_types)

            # 添加该题型的下一个题目
            question = type_groups[selected_type][type_indices[selected_type]][1]
            result.append(question)
            type_indices[selected_type] += 1

        return result

    def _adjust_difficulty(self, questions, difficulty_distribution):
        """调整难度分布"""
        result = []
        remaining_questions = questions.copy()

        for difficulty, target_count in difficulty_distribution.items():
            difficulty_questions = [
                q for q in remaining_questions
                if q['effective_difficulty'] == difficulty
            ]

            actual_count = min(target_count, len(difficulty_questions))
            selected = random.sample(difficulty_questions, actual_count)
            result.extend(selected)

            # 从剩余题目中移除
            for q in selected:
                remaining_questions.remove(q)

        # 添加剩余题目
        result.extend(remaining_questions)

        return result


class ExaminationAnalyzer:
    """考试分析器"""

    @staticmethod
    def analyze_exam_quality(examination):
        """分析考试质量"""
        questions = examination.questions.all()

        if not questions.exists():
            return {
                'overall_score': 0,
                'difficulty_balance': 0,
                'type_diversity': 0,
                'score_distribution': 0,
                'recommendations': []
            }

        # 难度平衡性分析
        difficulty_balance = ExaminationAnalyzer._analyze_difficulty_balance(questions)

        # 题型多样性分析
        type_diversity = ExaminationAnalyzer._analyze_type_diversity(questions)

        # 分值分布分析
        score_distribution = ExaminationAnalyzer._analyze_score_distribution(questions)

        # 总体评分
        overall_score = (difficulty_balance + type_diversity + score_distribution) / 3

        # 生成建议
        recommendations = ExaminationAnalyzer._generate_recommendations(
            difficulty_balance, type_diversity, score_distribution, questions
        )

        return {
            'overall_score': round(overall_score, 2),
            'difficulty_balance': round(difficulty_balance, 2),
            'type_diversity': round(type_diversity, 2),
            'score_distribution': round(score_distribution, 2),
            'recommendations': recommendations
        }

    @staticmethod
    def _analyze_difficulty_balance(questions):
        """分析难度平衡性"""
        difficulties = [q.effective_difficulty for q in questions]

        if not difficulties:
            return 0

        avg_difficulty = sum(difficulties) / len(difficulties)
        ideal_difficulty = 3.0  # 理想平均难度

        # 难度分数：越接近3.0分数越高
        difficulty_score = max(0, 1 - abs(avg_difficulty - ideal_difficulty) / 2)

        # 难度分布均衡性
        difficulty_counts = {}
        for d in difficulties:
            difficulty_counts[d] = difficulty_counts.get(d, 0) + 1

        # 计算分布均衡度
        if len(difficulty_counts) == 1:
            distribution_score = 0.3  # 只有单一难度
        else:
            counts = list(difficulty_counts.values())
            max_count = max(counts)
            min_count = min(counts)
            distribution_score = 1 - (max_count - min_count) / max_count

        return (difficulty_score + distribution_score) / 2

    @staticmethod
    def _analyze_type_diversity(questions):
        """分析题型多样性"""
        type_counts = {}
        for q in questions:
            q_type = q.question.type
            type_counts[q_type] = type_counts.get(q_type, 0) + 1

        total_questions = len(questions)
        type_count = len(type_counts)

        # 理想的题型数量是3-4种
        ideal_types = 3.5
        type_score = min(1, type_count / ideal_types)

        # 题型分布均衡度
        if type_count == 1:
            distribution_score = 0
        else:
            counts = list(type_counts.values())
            max_count = max(counts)
            min_count = min(counts)
            distribution_score = 1 - (max_count - min_count) / max_count

        return (type_score + distribution_score) / 2

    @staticmethod
    def _analyze_score_distribution(questions):
        """分析分值分布"""
        scores = [float(q.effective_score) for q in questions]

        if not scores:
            return 0

        total_score = sum(scores)
        avg_score = total_score / len(scores)

        # 检查分值是否合理（大部分题目应该在1-10分之间）
        reasonable_scores = len([s for s in scores if 1 <= s <= 10])
        score_reasonableness = reasonable_scores / len(scores)

        # 分值分布均衡性
        max_score = max(scores)
        min_score = min(scores)

        if max_score == min_score:
            distribution_score = 0.5
        else:
            # 理想的最大分值不超过最小分值的5倍
            ratio = max_score / min_score
            distribution_score = max(0, 1 - (ratio - 5) / 10)

        return (score_reasonableness + distribution_score) / 2

    @staticmethod
    def _generate_recommendations(difficulty_balance, type_diversity, score_distribution, questions):
        """生成改进建议"""
        recommendations = []

        # 难度建议
        if difficulty_balance < 0.6:
            difficulties = [q.effective_difficulty for q in questions]
            avg_difficulty = sum(difficulties) / len(difficulties)

            if avg_difficulty < 2.5:
                recommendations.append("建议增加一些中等或高难度题目以提高考试区分度")
            elif avg_difficulty > 3.5:
                recommendations.append("建议增加一些简单题目以确保考生能够完成基本题目")

        # 题型建议
        if type_diversity < 0.6:
            type_counts = {}
            for q in questions:
                q_type = q.question.type
                type_counts[q_type] = type_counts.get(q_type, 0) + 1

            if len(type_counts) < 3:
                recommendations.append("建议增加更多题型以提高考试内容的全面性")

        # 分值建议
        if score_distribution < 0.6:
            scores = [float(q.effective_score) for q in questions]
            max_score = max(scores)
            min_score = min(scores)

            if max_score / min_score > 10:
                recommendations.append("建议调整题目分值，避免分值差距过大")

        return recommendations


class ExamTimeManager:
    """考试时间管理器"""

    @staticmethod
    def calculate_optimal_duration(examination):
        """计算最佳考试时长"""
        questions = examination.questions.all()

        if not questions.exists():
            return 60  # 默认1小时

        # 基础时间（分钟）
        base_time = 0

        for q in questions:
            question = q.question
            effective_score = q.effective_score

            # 根据题目类型和分值计算时间
            if question.type in ['choice', 'true_false']:
                # 选择题：每分1分钟
                time_per_score = 1
            elif question.type == 'multiple_choice':
                # 多选题：每分1.5分钟
                time_per_score = 1.5
            elif question.type == 'fill_blank':
                # 填空题：每分1.2分钟
                time_per_score = 1.2
            elif question.type == 'short_answer':
                # 简答题：每分2分钟
                time_per_score = 2
            elif question.type in ['essay', 'calculation']:
                # 论述题/计算题：每分3分钟
                time_per_score = 3
            else:
                # 其他题型：每分1.5分钟
                time_per_score = 1.5

            base_time += float(effective_score) * time_per_score

        # 添加缓冲时间（20%）
        optimal_duration = int(base_time * 1.2)

        # 限制在合理范围内
        return max(30, min(240, optimal_duration))  # 30分钟到4小时

    @staticmethod
    def detect_anomalies(session):
        """检测考试异常行为"""
        anomalies = []

        if not session.exam_answers.exists():
            return anomalies

        answers = session.exam_answers.all()

        # 检测答题时间异常
        time_anomalies = []
        for answer in answers:
            if answer.time_spent < 5 and answer.answer:  # 答题时间少于5秒
                time_anomalies.append({
                    'question_id': str(answer.exam_question.id),
                    'type': 'fast_answer',
                    'time_spent': answer.time_spent,
                    'description': '答题时间异常短，可能存在作弊'
                })

        if len(time_anomalies) > len(answers) * 0.3:  # 超过30%的题目答题时间过短
            anomalies.append({
                'type': 'multiple_fast_answers',
                'count': len(time_anomalies),
                'description': '多个题目答题时间异常短'
            })

        # 检测答题模式异常
        answer_times = [a.time_spent for a in answers if a.time_spent > 0]
        if len(answer_times) > 5:
            avg_time = sum(answer_times) / len(answer_times)
            # 如果大部分题目答题时间非常接近，可能是复制粘贴
            time_variance = sum((t - avg_time) ** 2 for t in answer_times) / len(answer_times)
            if time_variance < 100:  # 方差很小
                anomalies.append({
                    'type': 'similar_answer_times',
                    'variance': time_variance,
                    'description': '答题时间模式异常，可能存在作弊'
                })

        # 检测IP地址变化
        if hasattr(session, 'ip_history') and len(session.ip_history) > 1:
            unique_ips = len(set(session.ip_history))
            if unique_ips > 1:
                anomalies.append({
                    'type': 'ip_changed',
                    'unique_ips': unique_ips,
                    'description': '考试期间IP地址发生变化'
                })

        return anomalies

    @staticmethod
    def generate_time_report(examination):
        """生成考试时间报告"""
        sessions = examination.exam_sessions.filter(status='completed')

        if not sessions.exists():
            return {
                'average_time': 0,
                'fastest_time': 0,
                'slowest_time': 0,
                'time_distribution': {},
                'recommendations': []
            }

        times = [s.time_spent for s in sessions if s.time_spent]

        if not times:
            return {
                'average_time': 0,
                'fastest_time': 0,
                'slowest_time': 0,
                'time_distribution': {},
                'recommendations': []
            }

        average_time = sum(times) / len(times)
        fastest_time = min(times)
        slowest_time = max(times)

        # 时间分布
        duration = examination.duration * 60  # 转换为秒
        time_distribution = {
            'very_fast': len([t for t in times if t < duration * 0.5]),
            'fast': len([t for t in times if duration * 0.5 <= t < duration * 0.7]),
            'normal': len([t for t in times if duration * 0.7 <= t < duration * 0.9]),
            'slow': len([t for t in times if t >= duration * 0.9])
        }

        # 生成建议
        recommendations = []
        if average_time < duration * 0.5:
            recommendations.append("考试时间可能过长，建议适当缩短考试时长或增加题目难度")
        elif average_time > duration * 0.9:
            recommendations.append("大部分考生时间紧张，建议延长考试时长或减少题目数量")

        very_fast_count = time_distribution['very_fast']
        if very_fast_count > len(times) * 0.2:
            recommendations.append("有较多考生完成时间很快，建议检查题目难度是否合适")

        return {
            'average_time': int(average_time),
            'fastest_time': int(fastest_time),
            'slowest_time': int(slowest_time),
            'time_distribution': time_distribution,
            'recommendations': recommendations
        }